/*
 * Shell.cpp
 *
 *  Created on:  May 29, 2009
 *      Author:  Zach Riggle A37627537
 *      Project: CSE410 Project 1
 */
#include <sys/types.h>
#include <sys/wait.h>
#include <pwd.h>
#include <unistd.h>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <fcntl.h>
#include "Log.h"
#include "Shell.h"
#include "ShellCommand.h"
#include "ChildProcess.h"

using namespace std;

Shell shell;

Shell::Shell() {
    quitShell = false;
    promptPid = 0;

    pipe(shellPipe);
    fcntl(shellPipe[0], F_SETFL, O_NONBLOCK);

    backgroundCounter = 0;
}

Shell::Shell(string firstCommand) {
    quitShell = false;
    promptPid = 0;
    backgroundCounter = 0;

    if (firstCommand.length() > 0)
        addCommand(firstCommand);
    prompt();
}

Shell::~Shell() {
}

void Shell::changeShellDirectory(const char* p) {
    write(shellPipe[STDOUT_FILENO], p, strlen(p)+1);
//	if (p != NULL && strlen(p) > 0) {
//		info("Changing directory to " << p);
//		cwd = p;
//	}
}

void Shell::checkChdir() {
    char buffer[PATH_MAX+1];
    read(shellPipe[STDIN_FILENO], buffer, PATH_MAX+1);
    info("Got CHDIR: " << buffer);
    chdir(buffer);
}

void Shell::executeCommands() {
    // Check for completed background commands, regardless of whether or not there
    // are commands to execute.
    printCompletedBackgroundCommands();

    while (commandsToExecute.size() > 0 && !quitShell) {
        // Check for completed background commands.
        printCompletedBackgroundCommands();

        // Pop the command off the queue
        string cmd = getNextCommand();

        // Create the command...
        ShellCommand *c = new ShellCommand(cmd, this);
        c->run();

        // Get the pid
        int pid = c->getPid();

        // Little bit of debug output
        if (pid == 0) {
            warn("Did not fork process");
            delete c;
        } else {
            debug("Forked pid: " << pid);

            // Check to see if we should wait for the process to complete?
            if (c->isBackground()) {
                c->setBackgroundNumber(++backgroundCounter);

                debug("Created a background process with pid [" << pid << "]");
                cout << "[" << c->getBackgroundNumber() << "] " << pid << endl;

                backgroundedCommands.push_back(c);
                return;
            }

            // Wait for the process to return.
            else {
                int stat;
                info("Waiting ShellCommand with pid " << pid);
                waitpid(pid, &stat, 0);
                if (WIFEXITED(stat) == 0)
                    warn("ShellCommand with pid " << pid << " did not return normally");
                debug("ShellCommand with pid " << pid << " returned " << WEXITSTATUS(stat));
            }
        }
        // Change the directory if needed.
        checkChdir();
    }
}

void Shell::printCompletedBackgroundCommands() {
    if (backgroundedCommands.size() == 0)
        debug("No background commands to check on.");

    // Loop over all of the backgrounded commands.
    //	int i =
    //	for (vector<ShellCommand*>::iterator i = backgroundedCommands.begin(); i
    //			!= backgroundedCommands.end(); i++) {
    for (int i = 0; i < backgroundedCommands.size(); i++) {
        ShellCommand* cmd = backgroundedCommands[i];

        debug("Checking on process " << cmd->getCommand());

        // If the command is still alive, skip it.
        if (cmd->isAlive())
            continue;

        // Place to hold the output message
        stringstream returnText;

        // Get the return value and signal.  One of these should be useful.
        int returnValue = cmd->getReturnValue();
        int signal = cmd->getSignal();

        // Check to see if it returned properly.
        if (returnValue != -1) {
            if (returnValue == 0)
                returnText << "Done";
            else
                returnText << "Exit " << returnValue;
            info("pid " << cmd->getPid() << " returned " << returnValue);
        }

        // Check to see if there was a signal.
        else if (signal != -1) {
            returnText << "Got signal " << ChildProcess::getSigName(signal);
            warn("pid " << cmd->getPid() << " " << returnText.str());
        }

        // Or it was something else
        else {
            returnText << "Exited abnormally";
            warn("pid " << cmd->getPid() << " " << returnText.str());
        }

        // Print it out
        string cmdPrint = "\t" + cmd->getCommand();
        cout << "[" << cmd->getBackgroundNumber() << "]+  " << returnText.str()
        << setw(24) << left << cmdPrint << endl;

        // Pop the pid off the queue, decrement I so as not to skip the next item.
        debug("Erasing from queue");
        delete cmd;
        backgroundedCommands.erase(backgroundedCommands.begin() + i);
        i--;
    }
    debug("At end of method.");
}

void Shell::prompt() {
    promptPid = getpid();
    quitShell = false;

    // Execute existing commands.
    executeCommands();

    while (!quitShell) {
        info("QuitShell: " << quitShell);

        // Show the prompt
        cout << Shell::getPrompt();

        // Grab the command
        string cmd;
        getline(cin, cmd, '\n');

        // Check for kill (Ctrl-D on my Mac)
        if (cin.eof()) {
            // Because we were on prompt line, print endl
            cout << endl;
            quitShell = true;
            warn("Caught kill");
            break;
        }

        // Add the command.
        addCommand(cmd);

        // Execute all available commands.
        executeCommands();
    }
}

string Shell::getPrompt() {
    stringstream prompt;
    prompt << "<" << (commandHistory.size() + 1) << " " << Shell::getHostname()
    << ":" << Shell::getUsername() << "> ";
    return prompt.str();
}

pid_t Shell::getPromptPID() {
    return promptPid;
}

string Shell::getHostname() {
    int bufferSize = 128;
    char *buffer = new char[bufferSize];
    gethostname(buffer, bufferSize);
    return buffer;
}

string Shell::getUsername() {
    passwd *pw;
    uid_t uid;

    uid = geteuid();
    pw = getpwuid(uid);
    if (pw == NULL) {
        error("Could not retrieve username");
        return "null";
    }

    return pw->pw_name;
}

void Shell::addCommand(string cmd) {
    if (cmd.size() > 0)
        commandsToExecute.push(cmd);
}

string Shell::getNextCommand() {
    if (commandsToExecute.size() > 0) {
        string cmd = commandsToExecute.front();
        commandsToExecute.pop();
        commandHistory.push_back(cmd);
        return cmd;
    }

    warn("Tried to get the next command when none were available.");
    return "";
}

void Shell::quit() {
    quitShell = true;
}

string Shell::getHistory() {
    stringstream ss;

    // Find the first entry
    int firstEntry = (commandHistory.size() >= 10 ? commandHistory.size() - 10
                      : 0);

    // Should definitely use iterators here.  Don't care.
    // This is not the grand prix.
    for (int i = firstEntry; i < commandHistory.size(); i++) {
        ss << (i + 1) << "\t" << commandHistory[i] << endl;
    }

    return ss.str();
}
